Skip to content

Python: feat(bedrock): implement native structured output support via Converse API#6052

Open
karthik-0306 wants to merge 6 commits into
microsoft:mainfrom
karthik-0306:fix-issue-5966
Open

Python: feat(bedrock): implement native structured output support via Converse API#6052
karthik-0306 wants to merge 6 commits into
microsoft:mainfrom
karthik-0306:fix-issue-5966

Conversation

@karthik-0306
Copy link
Copy Markdown

Motivation and Context

BedrockChatClient was the only chat client in MAF without structured output support. It actively blocked the feature by hardcoding response_format: None in BedrockChatOptions, silently discarding any schema passed by the caller regardless of what the user specified.
AWS Bedrock's Converse API added native structured output support via outputConfig.textFormat (GA February 4, 2026), making this workaround unnecessary. Every other MAF provider client — Anthropic, OpenAI, and Gemini — already supports response_format. This PR brings Bedrock to full parity.
Fixes #5966.

Description

Removed the response_format: None override in BedrockChatOptions so the field flows through from the parent ChatOptions naturally.
Added _prepare_output_config() which translates MAF's response_format (either a Pydantic model class or an OpenAI-style dict schema) into the exact wire format the Converse API requires:

json
{
  "outputConfig": {
    "textFormat": {
      "type": "json_schema",
      "structure": {
        "jsonSchema": {
          "name": "MyModel",
          "schema": "{...}",
          "description": "optional"
        }
      }
    }
  }
}

Added _set_additional_properties_false(), a recursive helper that mirrors the identical method in AnthropicChatClient. It walks the full schema tree and sets additionalProperties: false on every object type, as required by AWS for strict schema enforcement. A copy.deepcopy() guards against mutating the caller's original dict schema.
Threaded response_format through _process_converse_response() and _build_response_stream() so MAF's base class machinery handles response parsing and lazily hydrates ChatResponse.value with the validated Pydantic model — no custom JSON parsing logic needed.
Wrapped _invoke_converse() in a try/except that catches botocore.exceptions.ClientError. When AWS returns a ValidationException referencing outputConfig (which happens when a model like Claude 3.x, Nova, or Llama receives the parameter), it is re-raised as a descriptive ValueError naming the model and listing supported alternatives. No silent fallback to unstructured text.
When response_format is not provided, behaviour is identical to before — no outputConfig is sent and no existing functionality is affected.

Contribution Checklist

  • The code builds clean without any errors or warnings
  • The PR follows the Contribution Guidelines
  • All unit tests pass, and I have added new tests where possible
  • Approval mode is correctly propagated and enforced in progressive mode
  • Is this a breaking change? No — when response_format is omitted, the request is identical to before. No existing interfaces are modified.

Copilot AI review requested due to automatic review settings May 23, 2026 09:25
@github-actions github-actions Bot changed the title feat(bedrock): implement native structured output support via Converse API Python: feat(bedrock): implement native structured output support via Converse API May 23, 2026
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Adds structured output support for Bedrock Converse by translating response_format into Bedrock’s outputConfig.textFormat=json_schema, and ensures resulting ChatResponse.value is populated (including streaming), with tests covering schema wiring and an unsupported-model error path.

Changes:

  • Implement outputConfig generation from Pydantic models or dict-based JSON schemas and attach it to Converse requests.
  • Plumb response_format through response processing/stream building so ChatResponse.value can be parsed.
  • Add a new pytest suite validating wire shape, strict-schema behavior, streaming parsing, and a ValidationException-to-ValueError mapping.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 6 comments.

File Description
python/packages/bedrock/agent_framework_bedrock/_chat_client.py Adds outputConfig(json_schema) request support, strict schema mutation, and a clearer error for unsupported models.
python/packages/bedrock/tests/test_bedrock_structured_output.py Introduces tests for outputConfig shape, schema encoding, recursive strictness, parsing into .value, streaming, and unsupported-model handling.

Comment thread python/packages/bedrock/agent_framework_bedrock/_chat_client.py
Comment thread python/packages/bedrock/agent_framework_bedrock/_chat_client.py Outdated
Comment thread python/packages/bedrock/agent_framework_bedrock/_chat_client.py Outdated
Comment thread python/packages/bedrock/agent_framework_bedrock/_chat_client.py
Comment thread python/packages/bedrock/agent_framework_bedrock/_chat_client.py Outdated
Comment thread python/packages/bedrock/agent_framework_bedrock/_chat_client.py Outdated
@karthik-0306 karthik-0306 requested a review from Copilot May 23, 2026 16:24
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 5 comments.

Comment thread python/packages/bedrock/agent_framework_bedrock/_chat_client.py
Comment thread python/packages/bedrock/agent_framework_bedrock/_chat_client.py Outdated
Comment thread python/packages/bedrock/agent_framework_bedrock/_chat_client.py
Comment thread python/packages/bedrock/tests/test_bedrock_structured_output.py
Comment thread python/packages/bedrock/tests/test_bedrock_structured_output.py
@karthik-0306 karthik-0306 requested a review from Copilot May 23, 2026 16:53
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 5 comments.

Comment thread python/packages/bedrock/agent_framework_bedrock/_chat_client.py Outdated
Comment thread python/packages/bedrock/agent_framework_bedrock/_chat_client.py
Comment on lines +334 to +345
# "outputConfig" in error_message catches cases where Bedrock explicitly
# rejects the outputConfig field (unsupported model). Other ValidationExceptions
# (e.g. malformed schema shape, invalid property values) will not mention
# "outputConfig" and will bubble up as raw ClientError without being misdiagnosed.
if error_code == "ValidationException" and (
"outputconfig" in error_message.lower() or "outputconfig" in str(e).lower()
):
raise ValueError(
f"Model '{self.model}' does not support structured output via outputConfig.textFormat. "
"Check the model's Bedrock Converse outputConfig/textFormat support. "
f"AWS error Code: {error_code}. AWS error Message: {error_message}"
) from e
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Checked the existing MAF exception hierarchy — there is no UnsupportedFeature-style exception in the codebase. Two options: use the existing ChatClientInvalidRequestException which semantically fits ("the model rejected this request configuration"), or keep ValueError since it's standard Python for bad argument values and is consistent with how other validation errors are surfaced across MAF. Flagging for human reviewer input before making this call — happy to go either direction.

Comment thread python/packages/bedrock/tests/test_bedrock_structured_output.py
Comment thread python/packages/bedrock/agent_framework_bedrock/_chat_client.py
@moonbox3
Copy link
Copy Markdown
Contributor

Python Test Coverage

Python Test Coverage Report •
FileStmtsMissCoverMissing
packages/bedrock/agent_framework_bedrock
   _chat_client.py44510277%304–305, 321–330, 336, 354, 404, 413, 424, 426, 428, 433, 452–453, 477, 490, 502, 505, 513–514, 517–518, 520–521, 526–528, 530, 540–541, 563, 570, 579–580, 582–583, 585–587, 589, 591–592, 598–600, 603–604, 610–613, 619–629, 632, 651, 656, 701–702, 715, 741, 753, 758, 780, 782–783, 786, 790–791, 794, 815, 841, 855, 859, 873, 881–882, 886, 888–895
TOTAL36358431188% 

Python Unit Test Overview

Tests Skipped Failures Errors Time
7250 34 💤 0 ❌ 0 🔥 1m 51s ⏱️

Copy link
Copy Markdown
Contributor

@giles17 giles17 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated Code Review

Reviewers: 4 | Confidence: 86%

✓ Correctness

The implementation is correct and follows the established patterns from other MAF client implementations (Anthropic, OpenAI, Gemini). The response_format is properly threaded through both streaming (_build_response_stream with response_format kwarg) and non-streaming (_process_converse_response passing response_format to ChatResponse constructor) paths. The _set_additional_properties_false walker correctly handles cycles and respects existing additionalProperties dict schemas. The error handling in _invoke_converse properly catches and reclassifies ClientError for unsupported models. No correctness bugs were found.

✓ Security Reliability

The structured output implementation is well-designed from a security/reliability standpoint. Input validation is present for the main type-check case (non-dict, non-BaseModel raises TypeError), deep copy guards against mutating caller schemas, and the recursive schema walker uses cycle detection. The error handling in _invoke_converse correctly accesses botocore ClientError's guaranteed .response attribute and re-raises non-matching exceptions. The one notable reliability gap (ValueError vs library exception hierarchy) is already tracked in the unresolved review thread. I found one additional minor input validation gap at a trust boundary.

✓ Test Coverage

The test file provides solid coverage for the main happy paths (Pydantic model, OpenAI-style dict, streaming, non-streaming, error case). However, there are three meaningful coverage gaps: (1) no test verifying that non-outputConfig ValidationExceptions propagate as ClientError (the 'raise' pass-through path), (2) no test for the dict schema 'Shape B' code path ({"name": ., "schema": ...} without the json_schema wrapper), and (3) no test verifying that copy.deepcopy prevents mutation of the caller's original dict schema.

✗ Design Approach

I found one design-level contract mismatch. The new Bedrock structured-output path narows response_format from the framework-wide Mapping[str, Any] | BaseModel | None contract down to concrete dict inputs only, so valid mapping-based schemas that other parts of the framework accept will now fail on this provider.


Automated review by giles17's agents

Comment thread python/packages/bedrock/tests/test_bedrock_structured_output.py
Comment thread python/packages/bedrock/agent_framework_bedrock/_chat_client.py Outdated
@karthik-0306
Copy link
Copy Markdown
Author

All feedback addressed in commit 5145466 — isinstance(response_format, dict) widened to Mapping with dict() normalization in Shape C, description line updated to match, and 4 new tests added covering the Mapping path, Shape B, deepcopy mutation guard, and ClientError pass-through.

@karthik-0306 karthik-0306 requested a review from giles17 June 1, 2026 03:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Python: [Feature]: Python: BedrockChatClient — support Converse API outputConfig.textFormat for structured output

4 participants